﻿#include "livewatch.h"
#include <QComboBox>
#include <QEvent>
#include <QModelIndex>
#include <QLineEdit>
#include <QDebug>

namespace {
/*
 * 因为平时使用的变量名其实都是对应着内存中的地址的，因此需要建立一个变量名和内存地址的映射关系，
 * 同时变量名的显示格式也建立一个这样映射关系：
*/
    //变量名，模型中的变量名是以地址方式存放的。假设我们定义了以下变量
    constexpr int varColumn[3] = {1000, 2000, 1060};    //变量地址（实际存入模型中的值）
    const QString varStr[3] = {QString("click"), QString("size"), QString("volume")};//变量名
    //变量值格式采用下拉框编辑（这也是对应的模型内存入的真正数据），下拉框对应的数据含义，即下拉框的索引0代表10进制
    constexpr int formatColumn[3] = {10, 2, 16};
    const QString formatStr[3] = {QString("10进制"), QString("2进制"), QString("16进制")};
}

//------------------------------以下为 WatchDelegate 类的实现------------------------------


/*******************************************************************
 * 函数名称: MonitorDelegate()
 * 功能描述: 构造函数
 *******************************************************************/
WatchDelegate::WatchDelegate(QObject *parent/* = 0*/)
    :QItemDelegate(parent), tailIndex(Q_NULLPTR)
{

}
/*******************************************************************
 * 函数名称: ~MonitorDelegate()
 * 功能描述: 析构函数
 *******************************************************************/
WatchDelegate::~WatchDelegate()
{
    delete tailIndex;
    tailIndex = Q_NULLPTR;
}

/*******************************************************************
 * 函数名称: createEditor()
 * 功能描述: 生成编辑器
 *******************************************************************/
QWidget *WatchDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                                     const QModelIndex &index ) const
{
    if(index.column() == WatchModel::formatPos)//变量值格式列采用下拉框编辑
    {
        QComboBox *comboBox = new QComboBox(parent);
        comboBox->addItem(formatStr[0], formatColumn[0]);
        comboBox->addItem(formatStr[1], formatColumn[1]);
        comboBox->addItem(formatStr[2], formatColumn[2]);
        return(comboBox);
    }
    else if(index.column() == WatchModel::varPos
            || index.column() == WatchModel::valuePos)
    {   //变量名列和变量值式列采用 QLineEdit 编辑
        QLineEdit *lineEdit = new QLineEdit(parent);
        return(lineEdit);
    }

    return QItemDelegate::createEditor(parent, option, index);     //其他列采用默认编辑器
}
/*******************************************************************
 * 函数名称: setEditorData()
 * 功能描述: 将模型里的数据提取到编辑器中
 *******************************************************************/
void WatchDelegate::setEditorData(QWidget *editor, const QModelIndex &index ) const
{
    if(index.column() == WatchModel::formatPos)   //变量值格式列
    {
        QComboBox *comboBox = dynamic_cast<QComboBox *>(editor);
        comboBox->setCurrentIndex(comboBox->findData(index.data(Qt::EditRole)));
    }
    else if(index.column() == WatchModel::varPos
            || index.column() == WatchModel::valuePos) //变量名列和变量值格式列
    {
        QLineEdit *lineEdit = dynamic_cast<QLineEdit*>(editor);
        //若是最后一行：<click to add>，那么就将原有的文本清掉，否则正常获取index中的文本内容
        (index != (*tailIndex)) ? lineEdit->setText(index.data(Qt::DisplayRole).toString())    //尾行<clickToAdd>
                                : lineEdit->clear();
    }
    else
        QItemDelegate::setEditorData(editor, index);
}
/*******************************************************************
 * 函数名称: setModelData()
 * 功能描述: 设置模型数据
 *******************************************************************/
void WatchDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                 const QModelIndex &index ) const
{
    if(index.column() == WatchModel::formatPos)//变量值格式列
    {   //变量值本身就是int类型，所以直接采用 Qt::EditRole 设置模型数据比较开
        QComboBox *comboBox = dynamic_cast<QComboBox *>(editor);
        model->setData(index, comboBox->currentData(), Qt::EditRole);
    }
    else if(index.column() == WatchModel::varPos) //变量名列
    {
        QLineEdit *lineEdit = dynamic_cast<QLineEdit*>(editor);

        if(false == lineEdit->isModified()) return;    //如果内容没有发生更改，就直接返回

        if(lineEdit->text().isEmpty() && index != (*tailIndex))
        {   //若内容为空，且不是最后一行，那么就删除该行
            model->removeRows(index.row(), 1);
            return;
        }
        //剩下的要么内容不为空，要么是最后一行
        if(index != (*tailIndex))
            model->setData(index, lineEdit->text(), Qt::DisplayRole);
        else if(!(lineEdit->text().isEmpty()) )   //最后一行可能要追加一行
            emit clickToAdd(lineEdit->text());
    }
    else if(index.column() == WatchModel::valuePos)   //参数值列
    {   //如果参数值列也发了编辑，那么就是可能要写参数
        QLineEdit *lineEdit = dynamic_cast<QLineEdit*>(editor);

        if(false == lineEdit->isModified()) return;    //如果内容没有发生更改，就直接返回

        WatchModel *monitorModel = dynamic_cast<WatchModel *>(model);
        monitorModel->setData(index, lineEdit->text(), Qt::DisplayRole);
    }
    else
        QItemDelegate::setModelData(editor, model, index);
}
/*******************************************************************
* 函数名称: updateEditorGeometry()
* 功能描述: 设置编辑器组件大小
*******************************************************************/
void WatchDelegate::updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem &option,
                                         const QModelIndex &index ) const
{
    editor->setGeometry(option.rect);
}
/*******************************************************************
 * 函数名称: paint()
 * 功能描述: 自定义数据显示方式
 *******************************************************************/
void WatchDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option,
                          const QModelIndex &index ) const
{
    QItemDelegate::paint(painter, option, index);
}
/*******************************************************************
 * 函数名称: editorEvent()
 * 功能描述: 编辑器事件
 *******************************************************************/
bool WatchDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
                                const QStyleOptionViewItem &option, const QModelIndex &index )
{
    return QItemDelegate::editorEvent(event, model, option, index);
}
/*******************************************************************
 * 函数名称: setTailIndex()
 * 功能描述: 设置尾行模型索引
 * 函数参数: index - 尾行<click to add>模型项的索引
 *******************************************************************/
void WatchDelegate::setTailIndex(const QModelIndex &index)
{
    if(!index.isValid())
        return;

    if(Q_NULLPTR == tailIndex)
        tailIndex = new QPersistentModelIndex();

    (*tailIndex) = index;
}


//------------------------------以下为 WatchModel 类的实现------------------------------


/*******************************************************************
 * 函数名称: WatchModel()
 * 功能描述: 构造函数
 *******************************************************************/
WatchModel::WatchModel(QObject *parent/* = Q_NULLPTR*/)
    :QAbstractItemModel(parent), tab{new QVector<QVector<quint16> >()}
{
    VHeader << "variable" << "Value" << "Format";   //列标题

    tab->append(QVector<quint16>(columns));   //先将尾行添加
}
/*******************************************************************
 * 函数名称: ~WatchModel()
 * 功能描述: 析构函数
 *******************************************************************/
WatchModel::~WatchModel()
{
    delete tab;
    tab = Q_NULLPTR;
}

/*******************************************************************
 * 函数名称: headerData()
 * 功能描述: 获取表头信息
 *******************************************************************/
QVariant WatchModel::headerData(int section, Qt::Orientation orientation,
                                int role/* = Qt::DisplayRole*/) const
{
    if(role != Qt::DisplayRole)   //目前表头只支持Qt::DisplayRole类型项角色
        return QVariant();

    if(orientation == Qt::Vertical)
        return QString("%1").arg(section + 1);   //垂直标题为数字（从1开始）
    else
        return VHeader.at(section);              //水平标题
}
/*******************************************************************
 * 函数名称: setHeaderData()
 * 功能描述: 设置表头
 *******************************************************************/
bool WatchModel::setHeaderData(int section, Qt::Orientation orientation,
                               const QVariant &value, int role/* = Qt::EditRole*/)
{
    if((role != Qt::EditRole) || (orientation == Qt::Vertical))
        return false;

    VHeader.replace(section, value.toString());
    emit headerDataChanged(orientation, section, section);//更改完后要发送更改信号
    return true;
}

/*******************************************************************
 * 函数名称: index()
 * 功能描述: 返回父节点 parent 下子节点的索引
 *******************************************************************/
QModelIndex WatchModel::index(int row, int column,
                              const QModelIndex &parent/* = QModelIndex()*/) const
{
    if((row < 0) || (row >= tab->length()) ||
       (column < 0) || column >= columns)
        return QModelIndex();
    //createIndex()用于创建一个模型索引，刚刚看到这个函数还是很疑惑：
    //我只提供了一个行列号，它怎么就帮我建好模型索引了呢？这个索引可是
    //要访问模型项的啊？它我设计的模型内的数据长什么样吗？其实写到data()
    //函数就明白，对索引的解释是date()函数进行的，也就是说这里创建的模型
    //索引的具体含义要由我们自己解释。
    return createIndex(row, column);
}
/*******************************************************************
 * 函数名称: parent()
 * 功能描述: 通过子节点索引获取父节点索引，因为是表格模型，所以我们直接返回一个无效节点
 *******************************************************************/
QModelIndex WatchModel::parent(const QModelIndex &child) const
{
    return QModelIndex();
}
/*******************************************************************
 * 函数名称: rowCount()
 * 功能描述: 获取父节点 parent 下子节点的行数
 *******************************************************************/
int WatchModel::rowCount(const QModelIndex &parent/* = QModelIndex()*/) const
{
    return tab->length();
}
/*******************************************************************
 * 函数名称: columnCount()
 * 功能描述: 获取父节点 parent 下子节点列数
 *******************************************************************/
int WatchModel::columnCount(const QModelIndex &parent/* = QModelIndex()*/) const
{
    return columns;
}
/*******************************************************************
 * 函数名称: data()
 * 功能描述: 根据项角色 role 获取节点 index 数据。
 * 注意：本函数只支持 Qt::EditRole 和 Qt::DisplayRole两个项角色，
 * 其中 Qt::EditRole 是真正保存在模型中的，Qt::DisplayRole仅仅是提供给视图显示的
 *******************************************************************/
QVariant WatchModel::data(const QModelIndex &index, int role/* = Qt::DisplayRole*/) const
{
    if((!index.isValid()) || (index.row() >= tab->length()) ||
       (index.column() >= columns) || (role != Qt::EditRole && role != Qt::DisplayRole))
        return QVariant();

    if(role == Qt::EditRole)      //Qt::EditRole
        return(tab->at(index.row()).at(index.column()));

    if(index.row() < tab->length() - 1)       //正常显示变量的行
        return editToDisplay(index.row(), index.column());
    else if(index.row() == (tab->length() - 1) && index.column() == varPos)   //最后一行<click to add>的显示
        return(QString("<click to add>"));
    else if(index.row() == (tab->length() - 1) && index.column() != varPos)   //最后一行
        return(QString(""));
    else
        return QVariant();
}
/*******************************************************************
 * 函数名称: WatchModel::setData()
 * 功能描述: 设置模型项数据
 *******************************************************************/
bool WatchModel::setData(const QModelIndex &index, const QVariant &value, int role/* = Qt::EditRole*/)
{
    quint16 data;        //需要保存的数据

    if((!index.isValid()) || (index.row() >= tab->length()) ||
       (index.column() >= columns) )
        return false;

    if(role == Qt::EditRole)
    {   //Qt::EditRole 类型可以直接存入
        data = value.toUInt();
        (*tab)[index.row()].replace(index.column(), data);
    }
    else if(role == Qt::DisplayRole)
    {   //将Qt::DisplayRole转化为 Qt::EditRole 类型在存入
        data = displayToEdit(value.toString(), index.row(), index.column() );
        (*tab)[index.row()].replace(index.column(), data);
    }
    else
        return false;
    //修改完后一定要发送dataChanged()信号
    emit dataChanged(index, index, QVector<int>() << role);
    return true;
}
/*******************************************************************
 * 函数名称: flags()
 * 功能描述: 返回 index 的模型项标志（如是否可编辑等）
 *******************************************************************/
Qt::ItemFlags WatchModel::flags(const QModelIndex &index) const
{
    if((!index.isValid()) || (index.row() >= tab->length()) ||
       (index.column() >= columns) )
        return Qt::NoItemFlags;

    if((index.row() == tab->length() - 1) && index.column() != varPos)
        return (Qt::NoItemFlags | Qt::ItemIsSelectable);    //最后一行除了第0个元素，其他不可编辑
    //除最后一行外其他行数据都可编辑
    return (QAbstractItemModel::flags(index) | Qt::ItemIsEditable);
}
/*******************************************************************
 * 函数名称: insertRows()
 * 功能描述: 插入 count 行数据，成功返回 true，失败返回 false
 * If row is 0, the rows are prepended to any existing rows in the parent.
 * If row is rowCount(), the rows are appended to any existing rows in the parent.
 *******************************************************************/
bool WatchModel::insertRows(int row, int count, const QModelIndex &parent/* = QModelIndex()*/)
{
    QVector<quint16> rowData(columns);   //需要插入的默认行

    if(row < 0 || row > tab->length() || count < 1)
        return false;

    rowData[varPos] = varColumn[0];
    rowData[valuePos] = 0;
    rowData[formatPos] = formatColumn[0];

    beginInsertRows(parent, row, row + count - 1);
    for(int i = 0; i < count; ++i)
        tab->insert(row, rowData);
    endInsertRows();     //beginInsertRows(),endInsertRows()一定要一起用，不然程序会崩溃的

    return true;
}
/*******************************************************************
 * 函数名称: removeRows()
 * 功能描述: 删除 count 行数据，成功返回 true，失败返回 false
 * 注意，本函数不会删除最后一行，不能删掉 <click to add>
 *******************************************************************/
bool WatchModel::removeRows(int row, int count, const QModelIndex &parent/* = QModelIndex()*/)
{
    if(row < 0 || row > tab->length() || count < 1)
        return false;

    if(row + count >= tab->length())    //检查删除范围是否已超过总长度
        count = tab->length() - row - 1;//不能删掉最后一行

    if(count <= 0) return false;

    beginRemoveRows(parent, row, row + count - 1);
    tab->remove(row, count);
    endRemoveRows();     //beginRemoveRows(), endRemoveRows()一定要一起用，不然程序会崩溃的

    return true;
}
/*******************************************************************
 * 函数名称: getTailIndex()
 * 功能描述: 获取最后一行<click to add>的索引
 *******************************************************************/
QModelIndex WatchModel::getTailIndex(void) const
{
    int row = 0;

    if(!tab->isEmpty()) row = tab->length() - 1;
    return index(row, varPos, QModelIndex());
}
/*******************************************************************
 * 函数名称: appendRow()
 * 功能描述: 槽函数，追加一行
 * 函数参数: var - 变量名
 *******************************************************************/
void WatchModel::appendRow(QString name)
{
    int row = 0;
    QModelIndex i;    //新增行， varPos 列的索引
    if(!tab->isEmpty()) row = tab->length() - 1;
    insertRows(row, 1);
    i = index(row, varPos);
    setData(i, name, Qt::DisplayRole);
}
/*******************************************************************
 * 函数名称: editToDisplay()
 * 功能描述: 将Qt::EditRole 类型转为 Qt::DisplayRole。
 *          将 tab 中的第 row 行，第 column 列的模型项转为 Qt::DisplayRole类型
 *******************************************************************/
QString WatchModel::editToDisplay(int row, int column) const
{
    if(row < 0 || row >= tab->length() || column >= columns || column < 0)
        return QString();

    if(column == varPos)    //变量名
    {
        switch(tab->at(row).at(varPos))
        {
        case varColumn[0]:return varStr[0];
        case varColumn[1]:return varStr[1];
        case varColumn[2]:return varStr[2];
        default:return QString();
        }
    }
    if(column == valuePos)  //变量值
    {
        if((*tab)[row].at(formatPos) == formatColumn[0] ||
           (*tab)[row].at(formatPos) == formatColumn[1] ||
           (*tab)[row].at(formatPos) == formatColumn[2] )
            return( QString::number((*tab)[row].at(valuePos), (*tab)[row].at(formatPos)) );
    }
    if(column == formatPos)  //变量格式
    {
        switch(tab->at(row).at(formatPos))
        {
        case formatColumn[0]:return formatStr[0];
        case formatColumn[1]:return formatStr[1];
        case formatColumn[2]:return formatStr[2];
        default:return QString();
        }
    }
    return QString();
}
/*******************************************************************
 * 函数名称: displayToEdit()
 * 功能描述: 将 Qt::DisplayRole 类型转为 Qt::EditRole
 *           tab 中的第 row 行，第 column 列的模型项显示为display，本函数能将其转为对应的Qt::EditRole类型
 *******************************************************************/
quint16 WatchModel::displayToEdit(QString display, int row/* = 0*/,
                                  int column/* = 0*/) const
{
    if(row < 0 || row >= tab->length() || column >= columns || column < 0)
        return 0;

    if(column == varPos)    //参数地址
    {
        if(display == varStr[0]) return(varColumn[0]);
        if(display == varStr[1]) return(varColumn[1]);
        if(display == varStr[2]) return(varColumn[2]);
    }


    if(column == valuePos)     //参数值
    {
        if((*tab)[row].at(formatPos) == formatColumn[0] || (*tab)[row].at(formatPos) == formatColumn[1] ||
           (*tab)[row].at(formatPos) == formatColumn[2] )
            return( display.toInt(Q_NULLPTR, (*tab)[row].at(formatPos)) );
    }

    if(column == formatPos)    //参数格式
    {
        if(display == formatStr[0]) return(formatColumn[0]);
        if(display == formatStr[1]) return(formatColumn[1]);
        if(display == formatStr[2]) return(formatColumn[2]);
    }

    return 0;
}

/**********************************END OF FILE**********************************/
